package com.tools.common.io;

import cn.hutool.core.io.IORuntimeException;
import com.tools.common.container.RowKit;
import com.tools.common.object.Note;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * ZIP 压缩包包装类
 * */
@Note("ZIP 压缩包包装类")
public class ZipBean {

    @Note("压缩操作所需的字节输出流。该流必须是个 .zip 压缩包文件。")
    private ZipOutputStream compressStream;

    @Note("解压缩操作所需的字节输入流。该流必须是个 zip 压缩包文件")
    private ZipInputStream unCompressStream;

    @Note("解压缩的文件保存至这个目录里。该文件必须是个文件夹")
    private File unZipSavePath;

    @Note("压缩操作时使用: 要压缩的文件。可以是文件或者文件夹（文件夹会递归压缩下面的所有东西）")
    private List<File> files;

    /* *******************************************************************************************
     *
     *          构造器
     *
     * *******************************************************************************************
     * */

    public ZipBean() {}

    public static ZipBean of() {
        return new ZipBean();
    }

    /* *******************************************************************************************
     *
     *          Getter / Setter
     *
     * *******************************************************************************************
     * */

    public ZipOutputStream getCompressStream() {
        return compressStream;
    }

    public void setCompressStream(File savaPath) {
        private_checkSavePathAndMkdir(savaPath);
        this.compressStream = IOKit.zipOutputStream(savaPath);
    }

    public void setCompressStream(String savaPath) {
        File savePathFile = private_checkSavePathAndMkdir(savaPath);
        this.compressStream = IOKit.zipOutputStream(savePathFile);
    }

    public void setCompressStream(OutputStream savaPathStream) {
        if(savaPathStream == null) throw new NullPointerException();
        this.compressStream = IOKit.zipOutputStream(savaPathStream);
    }

    public ZipInputStream getUnCompressStream() {
        return unCompressStream;
    }

    public void setUnCompressStream(File srcZip) {
        private_checkSrcZipFile(srcZip);
        this.unCompressStream = IOKit.zipInputStream(srcZip);
    }

    public void setUnCompressStream(String srcZipPath) {
        File srcZipFile = private_checkSrcZipFile(srcZipPath);
        this.unCompressStream = IOKit.zipInputStream(srcZipFile);
    }

    public void setUnCompressStream(InputStream srcZipStream) {
        if(srcZipStream == null) throw new NullPointerException();
        this.unCompressStream = IOKit.zipInputStream(srcZipStream);
    }

    public File getUnZipSavePath() {
        return unZipSavePath;
    }

    public void setUnZipSavePath(File unZipSavePath) {
        private_checkUnZipSavePath(unZipSavePath);
        this.unZipSavePath = unZipSavePath;
    }

    public void setUnZipSavePath(String unZipSavePath) {
        this.unZipSavePath = private_checkUnZipSavePath(unZipSavePath);
    }

    public List<File> getNewFiles() {
        return new ArrayList<>(files);
    }

    List<File> getFiles() {
        return files;
    }

    public void setFiles(File file) {
        private_checkOneFile(file);
        this.files = new ArrayList<>(1);
        this.files.add(file);
    }

    public void setFiles(File... files) {
        int length = private_getArrayLength(files);
        if(length == 0) throw new NullPointerException("至少存在一个需要压缩的文件");
        this.files = new ArrayList<>(length);
        for (File f : files) {
            private_checkOneFile(f);
            this.files.add(f);
        }
    }

    public void setFiles(Iterable<?> files) {
        int size = RowKit.getIterableSize(files);
        if(size == 0) throw new NullPointerException("至少存在一个需要压缩的文件");
        this.files = new ArrayList<>(size);
        private_iterableAdd(files);
    }


    public void setFiles(String filePath) {
        this.setFiles(private_checkOneFile(filePath));
    }

    public void setFiles(String... filesPath) {
        int length = private_getArrayLength(filesPath);
        if(length == 0) throw new NullPointerException("至少存在一个需要压缩的文件");
        this.files = new ArrayList<>(length);
        for (String fp : filesPath) {
            this.files.add(private_checkOneFile(fp));
        }
    }
    
    
    public String toString() {
        return "ZipBean{" +
                "compressStream=" + compressStream +
                ", unCompressStream=" + unCompressStream +
                ", unZipSavePath=" + unZipSavePath +
                ", files=" + files +
                '}';
    }

    /* *******************************************************************************************
     *
     *          链式调用
     *
     * *******************************************************************************************
     * */

    public ZipBean compressStream(File savaPath) {
        this.setCompressStream(savaPath);
        return this;
    }

    public ZipBean compressStream(String savaPath) {
        this.setCompressStream(savaPath);
        return this;
    }

    public ZipBean compressStream(OutputStream savaPathStream) {
        this.setCompressStream(savaPathStream);
        return this;
    }

    public ZipBean unCompressStream(File srcZip) {
        this.setUnCompressStream(srcZip);
        return this;
    }

    public ZipBean unCompressStream(String srcZipPath) {
        this.setUnCompressStream(srcZipPath);
        return this;
    }

    public ZipBean unCompressStream(InputStream srcZipStream) {
        this.setUnCompressStream(srcZipStream);
        return this;
    }

    public ZipBean unZipSavePath(File unZipSavePath) {
        this.setUnZipSavePath(unZipSavePath);
        return this;
    }

    public ZipBean unZipSavePath(String unZipSavePath) {
        this.setUnZipSavePath(unZipSavePath);
        return this;
    }


    public ZipBean files(File file) {
        this.setFiles(file);
        return this;
    }

    public ZipBean files(File[] files) {
        this.setFiles(files);
        return this;
    }

    public ZipBean files(String filePath) {
        this.setFiles(filePath);
        return this;
    }

    public ZipBean files(String[] filesPath) {
        this.setFiles(filesPath);
        return this;
    }

    public ZipBean files(Iterable<?> files) {
        this.setFiles(files);
        return this;
    }

    /* *******************************************************************************************
     *
     *          自定义方法
     *
     * *******************************************************************************************
     * */
    
    public boolean useCompress() {
        return compressStream != null && files != null && !files.isEmpty();
    }
    
    public boolean useUnCompress() {
        return (unCompressStream != null) && (unZipSavePath != null);
    }
    
    public ZipBean addFile(File file) {
        private_checkOneFile(file);
        private_initFiles(1);
        this.files.add(file);
        return this;
    }

    
    public ZipBean addFile(String filePath) {
        File file = private_checkOneFile(filePath);
        private_initFiles(1);
        this.files.add(file);
        return this;
    }
    
    public ZipBean addFiles(File... files) {
        int length = private_getArrayLength(files);
        if(length == 0) return this;
        private_initFiles(length);
        for (File f : files) {
            private_checkOneFile(f);
            this.files.add(f);
        }
        return this;
    }

    
    public ZipBean addFiles(String... filesPath) {
        int length = private_getArrayLength(filesPath);
        if(length == 0) return this;
        private_initFiles(length);
        for (String fp : filesPath) {
            this.files.add(private_checkOneFile(fp));
        }
        return this;
    }

    
    public ZipBean addFiles(Iterable<?> files) {
        int size = RowKit.getIterableSize(files);
        if(size == 0) return this;
        private_initFiles(size);
        private_iterableAdd(files);
        return this;
    }

    
    public ZipBean removeFile(File file) {
        if(file == null || this.files == null || this.files.isEmpty()) return this;
        this.files.remove(file);
        return this;
    }

    
    public ZipBean removeFile(String filePath) {
        if(this.files == null || this.files.isEmpty()) return this;
        private_removeHandle(filePath);
        return this;
    }

    
    public ZipBean removeFile(int index) {
        if(files == null || files.isEmpty()) return this;
        if(index < 0 || index >= files.size()) return this;
        this.files.remove(index);
        return this;
    }

    
    public ZipBean removeFiles(File... files) {
        if(files == null || files.length == 0 || this.files == null || this.files.isEmpty()) return this;
        for (File f : files) this.files.remove(f);
        return this;
    }

    
    public ZipBean removeFiles(String... filesPath) {
        if(filesPath == null || filesPath.length == 0 || this.files == null || this.files.isEmpty()) return this;
        for (String fp : filesPath) private_removeHandle(fp);
        return this;
    }

    
    public ZipBean removeFiles(Iterable<?> files) {
        if(files == null) return this;
        for (Object o : files) {
            if (o instanceof File) this.files.remove(o);
            if (o instanceof String) private_removeHandle((String) o);
        }
        return this;
    }

    
    public ZipBean clearFiles() {
        if(files == null || files.isEmpty()) return this;
        this.files.clear();
        return this;
    }

    public int filesSize() {
        return (files == null) ? 0 : files.size();
    }

    public File getFile(int index) {
        if(files == null || files.isEmpty()) return null;
        if(index < 0 || index >= files.size()) return null;
        return files.get(index);
    }

    public boolean containsFile(File file) {
        if(file == null) return false;
        if(files == null || files.isEmpty()) return false;
        return files.contains(file);
    }

    public boolean containsFile(String filePath) {
        if(filePath == null || filePath.isEmpty()) return false;
        if(files == null || files.isEmpty()) return false;
        for (File f : files) if(f.getAbsolutePath().equalsIgnoreCase(filePath)) return true;
        return false;
    }

    /* *******************************************************************************************
     *
     *          私有逻辑
     *
     * *******************************************************************************************
     * */

    @Note("初始化要压缩的文件列表")
    private void private_initFiles(int initSize) {
        if(files == null) this.files = new ArrayList<>(initSize);
    }

    @Note("获取数组的长度")
    private static int private_getArrayLength(Object a) {
        if(a instanceof File[]) {
            return ((File[]) a).length;
        }
        if(a instanceof String[]) {
            return ((String[]) a).length;
        }
        return 0;
    }

    @Note("添加可迭代容器内的文件或文件路径")
    private void private_iterableAdd(Iterable<?> files) {
        for (Object o : files) {
            if(o instanceof File) {
                File f = (File) o;
                private_checkOneFile(f);
                this.files.add(f);
                continue;
            }
            if(o instanceof String) {
                String fp = (String) o;
                this.files.add(private_checkOneFile(fp));
                continue;
            }
            throw new IllegalArgumentException("被压缩的文件的容器入参只能是文件的实例或者其路径字符串。当前入参为: " + o);
        }
    }

    @Note("校验单个要压缩的文件或文件夹")
    private static void private_checkOneFile(File file) {
        if(file == null) throw new NullPointerException("要压缩的文件或目录为 null");
        if(!file.exists()) throw new IORuntimeException("要压缩的文件或目录不存在");
    }

    @Note("校验单个要压缩的文件或文件夹的路径")
    private static File private_checkOneFile(String filePath) {
        if(filePath == null || filePath.isEmpty()) throw new NullPointerException("要压缩的文件或目录的路径为空");
        File file = new File(filePath);
        if(!file.exists()) throw new IORuntimeException("要压缩的文件或目录不存在");
        return file;
    }

    @Note("判断路径是否相同的移除操作")
    private void private_removeHandle(String filePath) {
        if(filePath == null || filePath.isEmpty()) return;
        Iterator<File> iterator = files.iterator();
        while (iterator.hasNext()) {
            File f = iterator.next();
            if(f.getAbsolutePath().equalsIgnoreCase(filePath)) {
                iterator.remove();
                return;
            }
        }
    }

    @Note("压缩操作：目标压缩包文件实例的校验逻辑")
    private static void private_checkSavePathAndMkdir(File savaPath) {
        if(savaPath == null) throw new NullPointerException("目标压缩包的保存路径为空");
        if(FileKit.isDirectory(savaPath)) throw new IllegalArgumentException("压缩操作 File 实例必须是个压缩包文件");
        if(!savaPath.exists()) savaPath.getParentFile().mkdirs();
    }

    @Note("压缩操作：目标压缩包文件实例的校验逻辑")
    private static File private_checkSavePathAndMkdir(String savaPath) {
        if(savaPath == null || savaPath.isEmpty()) throw new NullPointerException("保存路径为空");
        File save = new File(savaPath);
        private_checkSavePathAndMkdir(save);
        return save;
    }

    @Note("解压缩操作：源压缩包的文件实例校验逻辑")
    private static void private_checkSrcZipFile(File srcZipFile) {
        if(srcZipFile == null) throw new NullPointerException("解压缩的目标压缩包文件为 null");
        if(!srcZipFile.exists()) throw new IllegalArgumentException("解压缩的目标压缩包文件不存在");
        if(srcZipFile.isDirectory()) throw new IllegalArgumentException("目标 File 实例不是一个压缩包文件");
    }

    @Note("解压缩操作：源压缩包的文件路径校验逻辑")
    private static File private_checkSrcZipFile(String srcZipFilePath) {
        if(srcZipFilePath == null || srcZipFilePath.isEmpty()) throw new NullPointerException("解压缩的目标压缩包文件路径为空");
        File file = new File(srcZipFilePath);
        private_checkSrcZipFile(file);
        return file;
    }
    
    
    @Note("解压缩操作：解压后的文件存放路径的校验逻辑")
    private static void private_checkUnZipSavePath(File unSavePath) {
        if(unSavePath == null) throw new NullPointerException("解压缩的文件保存的路径为 null");
        if(!unSavePath.exists()) {
            if(FileKit.isDirectory(unSavePath)) {
                unSavePath.mkdirs();
                return;
            }
            throw new IllegalArgumentException("解压缩的文件保存的路径只能是一个文件夹");
        }
    }

    @Note("解压缩操作：解压后的文件存放路径的校验逻辑")
    private static File private_checkUnZipSavePath(String unSavePath) {
        if(unSavePath == null || unSavePath.isEmpty()) throw new NullPointerException("解压缩的文件保存的路径为空");
        File unSaveFile = new File(unSavePath);
        private_checkUnZipSavePath(unSaveFile);
        return unSaveFile;
    }
}
